FrameLab v1.1.0: Basler/backend support, audit (complexity + bug fixes + tests), dependency pinning#7
Draft
andrefecto wants to merge 73 commits into
Draft
FrameLab v1.1.0: Basler/backend support, audit (complexity + bug fixes + tests), dependency pinning#7andrefecto wants to merge 73 commits into
andrefecto wants to merge 73 commits into
Conversation
* Fixed issues with recording not showing wireframes * Trying to fix a bug where cameras get initialized to a low resolution even though they're higher * Trying to fix a bug where camera FPS shows higher than possible * Moved the UI around a little to make the menu make sense * Added a bunch of per-camera settings * Fixed issues with the wireframe settings not taking effect * Fixed issues where having pose enabled by default didn't actually work
* Added the ability to configure camera settings like resolution, FPS, video codecc
## Bug Fixes: 1. **Video Loading** - Fixed TypeError when loading videos - Videos with None max_display dimensions now load correctly - Added None check before dimension comparison (camera.py:180) 2. **Camera UUID Consistency** - Fixed per-camera settings not applying - FPS display now uses camera_uuid instead of camera_id (camera.py:514) - Pose estimation settings use camera_uuid (camera.py:874-884) - Per-camera body parts, angles, and side view now persist correctly 3. **High-Resolution Camera Support** - Fixed 1080p/4K cameras on Windows - Windows default 640x480 resolution now overridden - Requests 4K (3840x2160) on init, falls back to camera max - 1080p and 4K cameras now use native resolution 4. **Video Memory Optimization** - Fixed potential memory issues - 4K video loading now limits display texture to 1920x1080 - Prevents oversized DearPyGUI textures - Improves UI responsiveness for high-res videos ## Documentation: - Created CLAUDE.md - comprehensive codebase guide for AI assistance - Added section markers to major files (camera.py, pose/, gui/, utils/) - Enhanced module docstrings with architecture notes - Documented commenting conventions for easier code navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Performance Enhancements: 1. **60 FPS Camera Support** - Removed artificial FPS throttling - Live cameras now run at native hardware FPS - Eliminates motion blur from frame rate limiting - Significantly improved smoothness for high-FPS cameras - Better GPU/CPU utilization (removed sleep delays) 2. **Enhanced Pose Estimation** - Upgraded MediaPipe model - Increased model_complexity from 1 to 2 (Heavy model) - Better accuracy at cost of more GPU usage - Disabled segmentation for better performance - GPU automatically optimized by TensorFlow Lite delegate 3. **Pose Smoothing** - Reduced wireframe jitter - Added exponential moving average (EMA) smoothing - Smoothing factor 0.3 balances responsiveness with stability - Increased tracking confidence from 0.5 to 0.7 - Angle measurements much easier to read ## Bug Fixes: 4. **Pose Estimation Disable Crash** - Fixed MediaPipe shutdown error - Fixed "packet timestamp mismatch" race condition - Proper shutdown: flag → wait 100ms → release resources - Added try-catch wrapper for graceful error handling - Clears smoothed landmarks cache on release 5. **Pixel Format Filtering** - Fixed invalid formats in dropdown - Improved FOURCC to string conversion for non-printable chars - Filters out formats with "?" or "Unknown" - Selected formats now persist correctly - Prevents restart failures from invalid codes ## UI Improvements: 6. **Adjustable Angle Arc Radius** - Configurable visualization size - Added angle_arc_radius setting (default: 20px, down from 30px) - New slider in Wireframe → Appearance (range: 10-80px) - Smaller arcs reduce clutter, easier to read angles - Persists in settings.json All changes extensively tested with 1080p/4K cameras at 60 FPS. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Significantly improved startup time on Windows systems by optimizing camera detection and initialization: - Use DirectShow backend (CAP_DSHOW) on Windows for 2-3x faster camera detection - Early exit after 2 consecutive failed camera indices (instead of checking all 10) - Reduced max camera indices to check from 10 to 8 - Reduced camera adjustment delays (0.1s → 0.05s) - Reduced frame read retry attempts (3 → 2) and delays (0.05s → 0.03s) Expected improvement: 70-80% reduction in startup time (from 15-20s to 3-5s) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add recording_lock to prevent race condition between capture thread and stop_recording() - Release pose estimator resources on exception to prevent MediaPipe leaks - Release VideoCapture on init failure to prevent handle leaks - Guard against empty frame buffer in toggle_live_pause() to prevent IndexError - Wrap frame buffer resize with frame_lock for thread safety - Add retry limit (300) for consecutive cap.read() failures to prevent infinite loop - Guard seek_to_frame against total_frames=0 - Use try/finally to ensure video_writer.release() on encoding failure - Extract _get_quadrant_sizes() helper replacing 4 duplicate calculations in layout.py - Extract _create_mouse_handlers() and _create_quadrant_content() eliminating ~170 duplicate lines - Remove dead create_dividers() function - Fix bare except clauses to use except Exception - Clean up handler registries on layout rebuild to prevent memory leak - Restore divider positions from settings on startup - Fix broken settings tests to use UUID-based API - Remove unused Path imports, fix misleading comment, simplify redundant conditional Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce CameraSource ABC so FrameLab can support industrial cameras (Basler GigE/USB3 Vision) alongside standard USB webcams. Detection now returns descriptors instead of bare ints, and the factory pattern selects the right backend at runtime. pypylon is an optional dependency — existing webcam-only setups are unaffected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use time-constant-based EMA instead of fixed alpha so landmark smoothing is consistent regardless of camera frame rate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix zoom label updates in live camera controls using configure_item instead of set_value (buttons ignore set_value) - Add missing tag to video pause button so slider-drag updates it - Fix off-by-one in video slider seek (was seeking past last frame) - Guard texture updates with does_item_exist during layout rebuilds - Protect frame buffer access with frame_lock to prevent race conditions - Use list(state.cameras) snapshots to prevent iteration errors - Replace O(n*m) nested loops with O(1) dict lookups in all handlers - Clamp live buffer slider values to [0.0, 1.0] - Fix get_position() denominator so video slider reaches 1.0 at end Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pylint setup: - Add .pylintrc with sensible defaults for DearPyGUI/OpenCV/MediaPipe - Fix import ordering (stdlib before third-party) across all modules - Remove unused imports and variables - Add explicit encoding to file open() calls - Remove redundant reimports (time module) - Score: 9.57/10 AI-friendliness improvements: - Replace magic tuples with LandmarkAdjustment NamedTuple in pose estimator - Extract DEFAULT_CAMERA_WIDTH/HEIGHT/FPS constants from magic numbers - Remove dead code (unused frozen_landmarks attribute) - Add type hints to public API methods (estimator, renderer, camera, settings) - Rename update_texture() to process_and_render_frame() (with alias) - Rename "None" string to "Unassigned" in quadrant combo UI - Add clarifying comment for Settings getter/setter asymmetry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update menu paths, keyboard shortcuts, settings format, angle list, Python version, project structure, and UI labels to match current code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lower default camera resolution from 4K to 1080p to prevent macOS AVFoundation from falling back to 1552x1552 square format. Show full UI with empty quadrants when no cameras are detected so users can still load videos. Add auto-dependency-update to launcher scripts and cache-clearing scripts for troubleshooting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cameras now use their native OS/driver defaults instead of forcing resolution/FPS/pixel format. This fixes initialization failures on Windows where the release/reopen cycle and DirectShow format negotiation caused cameras to fail on restart after settings changes. UUID fingerprint changed from resolution-dependent (unstable) to backend+index (stable across restarts). Old assignments are automatically migrated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DirectShow (CAP_DSHOW) defaults to 640x480 and requires explicit resolution requests. Media Foundation (CAP_MSMF) auto-negotiates the camera's best native resolution, matching AVFoundation behavior on macOS. Added a fallback for legacy backends that still default low. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fication On Windows, try Media Foundation first (auto-negotiates best resolution), fall back to DirectShow if unavailable. Store which backend worked in the camera descriptor so the same backend is used when creating the camera source. When the 1080p fallback is triggered, verify the stream actually works by reading a test frame — revert if it breaks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Uncompressed YUY2 at 1080p saturates USB bandwidth, capping cameras like the Razer Kiyo Pro at ~18 FPS. Setting MJPG (compressed) format before the resolution request allows full frame rate (30-60 FPS). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make Basler cameras the primary detection target with OpenCV webcams as fallback. Add native GenICam controls (exposure, gain, auto modes, FPS), USB bandwidth limiting for multi-camera setups, Basler-specific settings persistence, and a dedicated Camera Controls dialog in the UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix workflow push trigger (dev-v.1.1.0 -> dev-v1.1.0) so the dev branch actually runs CI; rename workflow to "CI" - Add a blocking pylint job (parallel to tests); both jobs cache pip - Add requirements-dev.txt (pinned pylint + pre-commit, over requirements.txt) - Add .pre-commit-config.yaml with a local pylint hook - Bring codebase to pylint 10.00/10: commented .pylintrc disables for framework conventions (DPG callback args, too-many-* family, state.py module globals) plus behavior-preserving code fixes Bonus fixes surfaced by pylint: - Remove ~225 lines of dead nested functions in create_menu_bar - Fix cell-var-from-loop bug: per-camera Settings/Proc-Amp dialogs showed the last camera's name in their titles - Rename Camera._apply_persisted_settings -> apply_persisted_settings - Fix pre-existing failing test (test_pose_renderer::test_initialization asserted attributes PoseRenderer never defined) - Update CLAUDE.md (CI/CD + setup) and add TODO.md tracking doc Verified locally (py3.11): pylint 10.00/10, 51 tests pass, pre-commit clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first real CI run (after the branch-typo fix) exposed a pre-existing gap: all 8 test_pose_estimator tests error with OSError: libGLESv2.so.2: cannot open shared object file because PoseEstimator() loads MediaPipe, which links libGLESv2 even on the CPU delegate, and the bare ubuntu runner lacks it. - Install libgl1/libegl1/libgles2 in the test job before pip install - Bump actions/checkout@v3->v4 and setup-python@v4->v5 (Node 20 deprecation) - Correct CLAUDE.md: pose tests need GL libs (not fully display-free) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One landmarker per camera had detect() + smoothing/adjustment state reachable from the capture thread (recording branch) and the main render thread with no synchronization; concurrent MediaPipe detect() during recording-with-pose is the intermittent-crash source. - Add a threading.Lock + _closed flag to PoseEstimator guarding process_frame, get_landmarks (now returns None for None/closed results), the four manual- adjustment setters, and release() (idempotent; waits for in-flight detect()) - Drop the time.sleep(0.1) hack in Camera.disable_pose_estimation (release() is now lock-safe) - Add concurrency + use-after-release regression tests No change to detection output, coordinates, smoothing, or recording semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rks) Manual pose adjustment didn't line up with visible joints when zoomed. The render loop detects on the zoomed-then-resized frame (landmarks in displayed space), but the editor re-detected on the raw, un-zoomed frame for hit-testing, so clicks tested against the wrong positions at zoom > 1. - Camera publishes current_landmarks (the dict it just drew) and exposes one transform, display_to_frame(), used for all mouse<->landmark conversions - PoseEditor.get_landmark_at_position reuses current_landmarks + display_to_frame instead of re-detecting (fixes alignment AND removes the redundant detect() - resolves TODO #4); update_drag uses the same transform - layout.py mouse handler updated to the new update_drag signature - Tests: pure display_to_frame mapping + new test_pose_editor.py hit-test No behavior change at zoom = 1; one fewer detect() call site (complements the thread-safety fix). Detection still runs on the cropped frame (out of scope). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Delete the no-op "Pose Estimation Quality" menu and the pose_max_width setting (PoseEstimator never downsampled); drop the dead camera.pose_max_width writes and the test assertion. Pose still runs at native resolution. - Delete gui/preferences_dialog.py (121 lines, never imported/instantiated) - Remove the unused update_texture alias and fix stale docstring/comment references to update_texture / _process_frame_for_display - Prune CLAUDE.md's now-empty "Dead / no-op features" section Pure removal, no behavior change. pylint 10.00/10, 57 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#5 1/3) Move whole functions out of the 2200-line layout.py: - gui/controls.py: create_video_controls, create_live_camera_controls - gui/divider.py: divider drag (hit-test, ghost line, mouse down/move/release) - gui/input_handlers.py: keyboard shortcuts, frame stepping, register_global_mouse_handlers, _create_mouse_handlers Cross-gui calls use function-local imports (existing codebase pattern) to avoid cycles. main.py imports register_global_mouse_handlers + step_frame_* from gui.input_handlers. layout.py: 2200 -> 1235 lines. Pure movement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These handlers logged many INFO lines per mouse click / keypress / joint drag, flooding logs at the default INFO level and burying real signal. Demoted the per-event spam to DEBUG: - gui/divider.py on_mouse_click_handler: the whole per-click block incl. the two 60-char "=" banners (the error log stays). - gui/input_handlers.py: KEY PRESS/RELEASE, Keyboard zoom, spacebar/number -key targeting, pause/play toggle, and the two image CLICK lines. - pose/pose_editor.py: Started/Converted/Finished dragging landmark. - gui/controls.py on_speed_change: callback + parsed-speed diagnostics. Kept at INFO the genuine state-change events: recording toggle + saved, screenshot toggle + saved, "Dividers repositioned and saved", and "Cleared all pose adjustments". Logging level only — no logic changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gui.quadrants had real, bug-prone logic with no tests. Added TestQuadrantManipulation: remove_quadrant's position-shift (a camera above the removed quadrant shifts down; the dual conditions pos>q and p<q must stay consistent), the inactive-quadrant no-op guard, move_camera_to_position swap + the KeyError on an absent key, toggle enable-sorts / disable-removes, and add_camera_to_quadrant eject + the "(None)"/"Load Video..." branches. Drives the functions over the state globals with gui.layout.rebuild_camera_layout and gui.quadrants.save_camera_positions patched out; snapshots/restores the three globals (active_quadrants is reassigned, so restore by reassignment) and sets them explicitly per test for order-independence. 13 tests in the file; full suite still green (no state leak). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The zoom/pan crop math was inline in process_and_render_frame and unobservable (output is always resized back to w x h), so its integer truncation and edge-clamping were untested. Extracted a pure static Camera._zoom_crop_box(w, h, zoom_level, cx, cy) -> (x1,y1,x2,y2) with the math moved verbatim (incl. the int()/`// 2` and the right/bottom re-adjustment); it returns the full frame for zoom <= 1.0 and the caller keeps its `if zoom_level > 1.0` guard. No behavior change. Added TestZoomCropBox: centered 2x box, left/right edge clamps (window shifted back so width is preserved), the no-zoom full-frame branch, and a non-divisible zoom (640/3 -> 213) that locks the truncation against a future "obvious cleanup". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Buffer scrubbing (camera/camera.py): step_buffer_forward/backward clamp at the last/first frame; seek_buffer_position maps 0..1 -> int(pct*(len-1)); all three no-op (return False, no mutation) when not paused or the buffer is empty (the empty-buffer guard prevents a negative index). Angles (pose/renderer.py): calculate_all_angles omits a joint with an incomplete landmark set (asserted absent, not zeroed) and rounds present ones to 1 decimal; calculate_angle on a zero vector must not return NaN (guards the +1e-6 / np.clip). Refreshed CLAUDE.md's test-coverage note (now 96 tests; lists the added coverage) and marked TODO #10 done. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First step of splitting the 1146-line Camera god-class via mixins (pure code movement, identical external API + threading). Moved the zoom/pan state actions and view-geometry transforms — _zoom_crop_box (static), zoom_in/out, reset_zoom, display_to_frame — to camera/_zoom.py; Camera now inherits ZoomMixin. Cross-mixin calls (process_and_render_frame's self._zoom_crop_box) resolve via MRO on the shared self. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moved video playback (play/pause/loop/speed/seek) and live frame-buffer scrubbing (toggle_live_pause/step_buffer_*/seek_buffer_position/get_position) to camera/_playback.py; Camera inherits PlaybackMixin. Pure movement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 3/6) Moved start_recording/stop_recording (MP4 encode + angles JSON export) and take_screenshot to camera/_recording.py; Camera inherits RecordingMixin. The recording_lock drain in stop_recording and the capture-thread append are unchanged (same instance lock/state). Dropped the now-unused os/datetime imports from camera.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moved enable_pose_estimation/disable_pose_estimation (and the 'from pose import PoseEstimator, PoseRenderer' dependency) to camera/_pose.py; Camera inherits PoseMixin. Detection still runs in the render/capture paths via the shared pose_* state. Same camera->pose import edge, just relocated — no new cycle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moved create_display_texture and process_and_render_frame (the main-thread render: frame_lock read, zoom crop via self._zoom_crop_box, pose overlay, FPS HUD, texture push) to camera/_render.py; Camera inherits RenderMixin. Dropped the now-unused dpg/numpy imports from camera.py (cv2 stays for initialize/_capture_loop). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moved the background capture thread (_capture_loop + start_capture) to camera/_capture.py; Camera inherits CaptureMixin. camera.py is now 415 lines (from 1146) — the spine: __init__, initialize/settings, position_key, naming, release. Dropped the now-unused `import time` from camera.py. Threading is unchanged: _capture_loop (bg thread) and process_and_render_frame (main thread) still share self.frame_lock/self.recording_lock and the same instance state — mixins only relocate the method source. Also: dropped the resolved C0302 (too-many-lines) .pylintrc disable — both tracked splits (#5 layout, #11 camera) are done and the largest file is now 519 lines. Added a "Map of the camera/ package" to CLAUDE.md and marked TODO #11 done. pylint 10.00/10, 96 tests pass, pre-commit clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update the 1.1.0 entry to match what actually ships: add 'Codebase audit & hardening' (thread-safe pose, zoom-aligned editing, native-res video texture, stable quadrant keying) and 'Security' (pinned deps + SHA-256 model verification) sections; refresh the test/CI bullets (96 tests, the blocking lint job). Remove now-false references to the deleted Preferences dialog and the removed Pose Estimation Quality feature, fix the FPS-toggle location, correct the dev-v1.1.0 branch typo, the date, and the 3.9-3.12 Python range. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Foundation for the in-app updater. New version.py is the single source of truth (__version__ = "1.1.0"); main.py logs it at startup and shows it in the viewport title. Installers now bump the Python floor 3.8 -> 3.9 (MediaPipe requirement), source the macOS .app CFBundleVersion / Linux .desktop Version from version.py instead of hardcoding 1.1.0, and warn (not fail) when git or a git checkout is missing (auto-update needs it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ate 2/7) Settings gains auto_check_updates (default True), skipped_update_version, and last_update_check (throttle), wired through load/save/setters; old settings files load the defaults (migration). Tests cover defaults, round-trip, and migration from a pre-update file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ate 3/7) parse_version/is_newer (numeric MAJOR.MINOR.PATCH, gets 1.10.0 > 1.9.0 right), fetch_latest_release (public GitHub Releases API over certifi-backed HTTPS via an injectable opener; all failures swallowed so startup never blocks; tag validated against ^v?\d+\.\d+\.\d+ before it could ever reach git), should_notify (auto-check + newer + not-skipped), and is_check_due (6h throttle for the unauth API). 17 unit tests, no network. UI/process wiring comes in later phases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
update_capability(repo_dir) probes (via an injectable subprocess runner) whether the updater can drive this install: git on PATH, a git work-tree with an origin remote, and a clean *tracked* tree (untracked/ignored files like settings.json/venv are fine; detached HEAD is allowed since the updater pins release tags). Returns (can_apply, reason) so the UI can offer "Update now" or fall back to manual instructions instead of failing. 5 mocked-git tests for each degrade path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ers (auto-update 5/7) Add update.sh / update.bat (the self-updater the app invokes on "Update now"): wait for the app PID to exit (settle + poll, ~30s ceiling), git fetch + checkout the released tag, pip install into the repo's venv, relaunch; on failure stay on the old ref and relaunch the old version. Windows runs in a visible console so git/pip progress shows. Critical fix: install.sh/.bat no longer *generate* run_framelab.* — those are tracked files, and the updater's `git checkout <tag>` aborts on any local modification, so a regenerated-vs-committed drift would break every update (the .bat even differed: %~dp0 vs %SCRIPT_DIR%). Installers now just chmod the committed launcher + update.sh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o-update 6/7) New gui/updates.py: a background daemon thread runs utils.updater.fetch (never blocks startup / breaks offline) and publishes the result into gui.state; main.py's render loop calls poll_and_prompt() once per frame to show — on the main thread, per DPG's UI-thread rule — a modal: "FrameLab vX is available" with Release notes / Update now / Later / Skip this version. If update_capability() says auto-update isn't possible (no git / dirty tree), the dialog shows manual `git pull` instructions instead of "Update now". "Update now" warns if recording, then spawns the committed update.sh/.bat detached (POSIX start_new_session; Windows CREATE_NEW_CONSOLE) with repo dir + tag + PID and calls dpg.stop_dearpygui() so the updater can take over. main.py kicks the auto-check after startup; File > Check for Updates runs it manually (force_show, ignoring throttle/skip, with "you're up to date" feedback). gui.updates added to the import-cycle guard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONTRIBUTING.md gains a "Releasing FrameLab (maintainers)" section: bump version.py, merge to main, and publish a GitHub Release with a matching v<version> tag (a push to main alone won't trigger an update). Documents the client update flow, the detached-HEAD state on updated installs, the committed-launcher invariant, and the git-clone prerequisite. CLAUDE.md gains a short auto-update pointer (updater module split + the don't-hand-edit-the- launchers sharp edge). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expand Basler support with white balance, gamma, black level, ROI/binning, and pixel-format controls (BaslerCameraSource + a tabbed controls dialog, gating absent nodes via has_node), plus full-config backup/restore to a .pfs file via pylon.FeaturePersistence (same format as the pylon Viewer). New per-control values persist in the 'basler' settings dict and are re-applied on startup in dependency order (binning -> ROI -> pixel format -> auto modes -> manual values -> gamma/black level -> fps); a .pfs restore applies live then reads the controls back so the import sticks through the normal replay path. ROI/binning resolution changes route through Camera.refresh_dimensions(), which recreates the display texture and rebuilds the layout to keep the texture-size invariant. Adds source-layer unit tests for all new methods. Also bundles pending FPS-overlay capture/render tweaks from a prior session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bring the README current with dev-v1.1.0: rewrite the installation/launch flow (committed run_framelab/update launchers, git check), add an "Updating FrameLab" section for the in-app auto-updater, expand the Basler/industrial camera coverage (tabbed GenICam controls + .pfs backup/restore), and refresh the now-stale Project Structure tree, dependencies, and contributing pointers. Add a tuned .markdownlint.json (disables line-length / inline-HTML / first-line heading / ordered-list-prefix rules that conflict with the repo's style; duplicate-heading siblings-only) so markdownlint runs clean (0 errors). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nnotations
Add a DartFish-style manual measurement mode as an alternative to automatic
pose detection: the user places N independent, labeled 3-point angle
measurements (arm-vertex-arm) and the angle at the vertex is shown live on the
frame. This mode never runs MediaPipe, so it sidesteps unreliable auto-detection
(bad angle/lighting) and the re-detection-overwrites-offsets problem.
- pose/manual_angles.py: pure, headless-testable model (AngleMeasurement +
ManualAngleTool) keyed by frame number; seed-and-drag handles; JSON round-trip.
- camera/_manual_angles.py mixin + Camera wiring: enable/disable/add and, for
video files, persist measurements to a {video}_manual_angles.json sidecar
(reload + re-edit on reopen). Live cameras are in-session only.
- Points stored in native pixels (zoom-independent) via new ZoomMixin
native_to_view/view_to_native; a dedicated displayed_frame index gives exact
frame keying. Overlay drawn after the pose block; mouse drag routed in
_create_mouse_handlers (edit only on a paused/seeked frame).
- Per-quadrant "Angles" button (video + live) opens a management dialog
(enable, add, relabel, live readout, delete, clear).
164 tests pass (17 new); pylint 10.00/10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Relocate TODO.md under docs/ and add docs/IDEAS.md capturing roadmap ideas (camera-control quadrant, bike-measurement export, quadrant pop-out, drag/aero calculations, athlete-history quadrant, navigation simplification). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scan with vulture (min-confidence 60) over the whole codebase; every candidate cross-checked with repo-wide grep (incl. tests) before deletion. - utils/settings.py: drop 9 zero-caller setters/getters (dialogs persist via direct attribute assignment). - camera/detection.py: delete the dead hardware-info subsystem (per-platform id helpers, _hardware_info_cache, get_camera_hardware_info, fourcc<->string converters) + now-unused platform/re/subprocess imports (417 -> 208 lines). - pose/estimator.py: drop per-landmark clear_manual_adjustment (the used method is clear_all_adjustments); utils/logger.py: drop log_exception; gui/divider.py: drop unused DIVIDER_* constants; gui/state.py: drop slider_active; camera/source.py: drop unread self._camera_id. - Remove the "Reset All to Defaults" camera feature entirely (dead AND silently broken: wrote phantom legacy attrs, never cleared the real camera_settings_by_uuid) — function, menu item, and wiring. - Tests: drop cases that only exercised removed test-only methods. - Docs: clean TODO.md, mark item #1 done; trim IDEAS.md; update CLAUDE.md. Verified: pylint 10.00/10, 162 tests pass, pre-commit clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FrameLab did no font setup, so DearPyGui fell back to its built-in bitmap font (ProggyClean) at ~13px — blocky and hard to read. Load a bundled anti-aliased TrueType font (Roboto) oversampled 2x and bind it globally. - gui/fonts.py: setup_fonts() (fail-safe — logs + falls back to the bitmap font on any error, never blocks startup) + DPG-free _resolve_font_path(). - main.py: call after dpg.setup_dearpygui(), before show_viewport(). - assets/fonts/: bundle Roboto-Regular/Bold.ttf + Apache-2.0 LICENSE. - tests/test_fonts.py + gui.fonts in the import-smoke list. First (font-legibility) slice of the "Easier navigation" idea (docs/IDEAS.md); tracked as item #2 in docs/TODO.md. pylint 10.00/10, 166 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a discoverable top-level "Quick Settings" menu (2nd, after File) with one-click checkboxes that apply to ALL cameras at once, removing the per-camera-dialog + Save-per-camera friction. - gui/dialogs/quick_settings.py: DPG-free set_pose_estimation_all() / set_fps_overlay_all() helpers (loop state.cameras, persist via settings) + thin on_pose_toggle_all / on_fps_toggle_all callbacks. The FPS handler sets the global default AND per-UUID overrides for live cameras so the toggle wins uniformly; video files follow the global default. - Centralized the pose path: removed wireframe.on_pose_toggle (moved into set_pose_estimation_all), repointed the Wireframe "Enable Pose Estimation" item to the shared callback (tag wf_pose_toggle); the callback syncs both the Wireframe and Quick Settings checkmarks so they never desync. Updated the gui/dialogs/__init__.py re-exports. - tests/test_quick_settings.py (pose enable/disable-all + FPS global/per-camera persistence); gui.dialogs.quick_settings added to the import-smoke list. Second slice of the "Easier navigation" idea (docs/IDEAS.md); tracked as item #3 in docs/TODO.md. pylint 10.00/10, 170 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reorganize the menu tree to reduce scanning/click-depth (7 -> 6 top-level menus). Pure reorder/relabel within create_menu_bar() — no behavior or callback changes. - New order: File | Quick Settings | Cameras | Pose | Quadrants | Help. - Remove the dead "View -> Stats" placeholder (only logged "not implemented"). - Fold the lone "Playback" menu into Cameras as "Playback Settings...". - Rename "Camera Settings" -> "Cameras" and "Wireframe" -> "Pose". - Move "Keyboard Shortcuts..." + "Check for Updates..." into a new Help menu. - Keep all item tags (main_menu_bar, qs_*, wf_pose_toggle, side_view_*) so the layout-rebuild and checkmark-sync paths keep working. Third slice of the "Easier navigation" idea (docs/IDEAS.md); tracked as item #4 in docs/TODO.md. pylint 10.00/10, 170 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add "Camera Controls" as a selectable quadrant content type so common per-camera actions aren't buried in menus. Picking it from a quadrant's dropdown replaces the feed with a compact panel that targets one live camera. Panel (launchers + quick toggles, no inline sliders): camera picker, Pose Estimation + Show FPS checkboxes, zoom -/+/Reset, and launcher buttons for the existing General Settings / Configure Joints / Camera Controls (Basler) or Video Proc Amp (webcam) dialogs. Reuses Camera methods + dialog entry points. - gui/control_panel.py: create_control_panel(quad_position). - gui/state.py: control_quadrants (set) + control_panel_camera_keys (session). - utils/settings.py: persisted global control_quadrants (init/load/save). - gui/quadrants.py: CONTROL_PANEL_OPTION + save_control_quadrants(); add/eject/ clear so a control panel and a camera are mutually exclusive per quadrant. - gui/layout.py: render the panel for control quadrants + dropdown option. - main.py: _restore_control_quadrants() at startup (control wins over a camera at the same slot), both no-camera and normal paths. - tests/test_control_panel.py (assignment logic + persistence round-trip); gui.control_panel added to the import-smoke list. Opt-in/persisted (no auto-placement on fresh installs). Promoted from docs/IDEAS.md; tracked as item #5 in docs/TODO.md. pylint 10.00/10, 176 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the loose setup + maintenance scripts out of the repo root: - install.sh / install.bat -> install/ - clear_cache.sh / .bat -> utilities/ Kept at root (load-bearing for auto-update): run_framelab.* and update.* — gui/updates.py spawns update.* from _REPO_ROOT and update.sh execs ./run_framelab.sh, so moving them would break the in-app updater. Each moved script now operates on the repo root (its parent): install.sh derives REPO_DIR=$SCRIPT_DIR/.. (cd, git -C, and the generated .app/.desktop launcher paths); install.bat derives REPO_DIR via pushd "%~dp0.." (cd + the shortcut target/working dir); both clear_cache.* cd to "..". Docs: README install commands + permission tip + Project Structure tree; CLAUDE.md note on the new layout and why the launchers stay at root. Scoped out moving the Python into a subfolder (would break imports/CI/installers). Promoted from docs/IDEAS.md; tracked as item #6 in docs/TODO.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add "Athlete History" as a selectable quadrant content type that browses the current athlete's recordings on disk and opens one with a click. Picked after the "Quadrants pop out" idea was dropped (DearPyGui is single-viewport — no real second OS window for another monitor; recorded in docs/IDEAS.md). Phase 1: - athlete/manager.py: list_recordings(athlete, bike) -> bike folder's *.mp4 as full paths, newest-first (+ convenience export). Filesystem-only. - gui/history_panel.py: create_history_panel() lists bikes (current expanded) -> recordings as buttons; clicking opens the video in a free feed quadrant via load_video_file (_target_quadrant_for_open). Refresh re-scans. - state.history_quadrants + persisted settings.history_quadrants; gui/quadrants gains HISTORY_PANEL_OPTION, save_history_quadrants, and a shared _clear_panel_modes() so control panels, history panels, and cameras are mutually exclusive per quadrant. gui/layout renders it + adds the dropdown option; main._restore_panel_quadrants restores both panel sets at startup. - tests/test_athlete_history.py (list_recordings + history-quadrant logic + persistence round-trip); gui.history_panel added to the import-smoke list. Remaining phases: thumbnails, screenshot/angle-JSON browsing, richer viewer. Tracked as item #7 in docs/TODO.md. pylint 10.00/10, 183 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a toggleable, zero-cost-when-off perf layer (utils/perf.py) gated by the FRAMELAB_PERF env var to diagnose Basler choppiness / wrong on-screen FPS: - capture thread: rolling delivery FPS + cap.read grab latency, and per-30-frame Basler stream-grabber buffer/drop stats with deltas - render: per-stage timing (copy/zoom/pose/cvtColor/normalize/set_value/whole), render-call FPS, and a log of which source the HUD FPS reflects - main loop: render_dearpygui_frame vs full-iteration time + loop FPS (exposes the DPG vsync ceiling) - basler_source: defensive get_grab_statistics() probing transport-specific nodes - main.py: quiet MediaPipe glog/clearcut telemetry so perf lines stay readable Add run_framelab_perf.sh/.bat launchers that set FRAMELAB_PERF=1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FrameLab v1.1.0 —
dev-v1.1.0→mainLarge release: 53 commits, 71 files, +9,664 / −1,856. Two bodies of work:
(A) the v1.1.0 feature/performance/platform work, and (B) a full codebase
audit (complexity reduction + bug hardening + tests + supply-chain pinning).
Opening as draft — being smoke-tested on the customer's machine tonight before marking ready.
A. Features / performance / platform
(
camera/source.py,source_factory.py,basler_source.py) — OpenCV and Basler behind one interface.fallback; MJPG for the 1080p path to fix low FPS; stabilized UUID generation (no more UUID churn
on replug/restart).
and resource-leak fixes, jitter/FPS fixes.
install.sh/install.bat), run scripts,configurable keyboard shortcuts, README/CHANGELOG/CONTRIBUTING.
B. Codebase audit (11 tracked items + security) — all complete
Goal: fewer places for bugs to hide, easier to navigate, no regressions.
Bug fixes (behavioral):
detect()behind a lock + idempotent release —fixes the intermittent crash/garbage when recording with pose enabled.
so click/drag lines up at any zoom (no second detection).
(
Camera.create_display_texture) — fixes broken/garbled rendering for sources above 1080p.state.camera_positionskeyed by a stableposition_key(UUID for live,synthetic id for videos) instead of
id(camera)— fixes mis-mapped/blank quadrants after GC /Load Video / reinit.
Structure (pure code movement, identical external API):
gui/layout.py2200 → 200 lines — split intomenu,controls,divider,input_handlers,camera_actions,keymap, and agui/dialogs/package (Automatic bike measurements #5, FrameLab v1.1.0: Basler/backend support, audit (complexity + bug fixes + tests), dependency pinning #7).camera/camera.py1146 → 415 lines — split into capture/render/zoom/playback/recording/posemixins (#11). Same flat instance namespace and threading, so all
camera.*access is unchanged.Security / supply-chain:
==) inrequirements*.txt.tampered or corrupt model).
Quality gates (now blocking in CI):
pylintenforced at 10.00/10; tests grew ~51 → 96; pre-commit hook added.detect()thread-safety,zoom geometry, buffer scrubbing, and a gui import-cycle guard.
Full per-item detail in
TODO.md; sharp edges documented inCLAUDE.md.✅ Verification
test+lintboth pass (Python 3.10, Ubuntu, with GL libs).import mainOK (Python 3.11 venv).🔧 Setup on the customer machine (important)
~/.cache/framelab/models/— needs network access once.🧪 Real-hardware smoke checklist (the behavior changes most worth confirming)
📝 Known doc follow-up (non-blocking)
CHANGELOG.mdpredates the audit — it still lists the removed Preferences dialog and the olddev-v.1.1.0branch name. Worth a sync pass before final release, but it doesn't affect runtime.🤖 Generated with Claude Code